﻿using System;
using Ast.Core.Services.Extensions;
using Ast.Framework;
using Ast.Framework.Caching;
using Castle.Core.Logging;

namespace Ast.Core.Services
{
    public interface ISymbolAddressFinder : IDependency
    {
        IntPtr FindAddress(string moduleName, string symbolName);
        IntPtr FindOffset(string symbolName);
        IntPtr FindAddressInNtModule(string symbolName);
    }

    public class SymbolAddressFinder : ISymbolAddressFinder
    {
        private readonly IPsLoadedModuleListService _psLoadedModuleListService;
        private readonly ISymbolService _symbolService;
        private readonly ICacheManager _cacheManager;

        public SymbolAddressFinder(IPsLoadedModuleListService psLoadedModuleListService, ISymbolService symbolService, ICacheManager cacheManager)
        {
            _psLoadedModuleListService = psLoadedModuleListService;
            _symbolService = symbolService;
            _cacheManager = cacheManager;

            Logger = NullLogger.Instance;
        }

        public ILogger Logger { get; set; }

        public IntPtr FindAddress(string moduleName, string symbolName)
        {
            var offset = FindOffset(symbolName);
            if (offset == IntPtr.Zero) return IntPtr.Zero;

            var modBase = IntPtr.Zero;
            _psLoadedModuleListService.ListModules((entry, path, name) =>
            {
                if (name.Equals(moduleName, StringComparison.InvariantCultureIgnoreCase))
                {
                    modBase = entry.DllBase;
                    return false;
                }
                return true;
            });
            if (modBase == IntPtr.Zero) return IntPtr.Zero;

            return modBase + offset.ToInt32();
        }

        public IntPtr FindOffset(string symbolName)
        {
            var symbolInfo = _symbolService.LoadSymbol(symbolName);
            return symbolInfo.HasValue ? new IntPtr((int) (symbolInfo.Value.Address - symbolInfo.Value.ModBase)) : IntPtr.Zero;
        }

        public IntPtr FindAddressInNtModule(string symbolName)
        {
            var ntModule = _cacheManager.Get("SymbolAddressFinder.FindAddressInNtModule.GetNtModule", context =>
            {
                var module = _psLoadedModuleListService.GetNtModule();
                if (module != null) _symbolService.LoadModule(module.Name, null, module.StartAddress); // ensure kernel symbol loaded
                return module;
            });
            return ntModule == null
                ? IntPtr.Zero
                : _cacheManager.Get("SymbolAddressFinder.FindAddressInNtModule." + symbolName, context => FindAddress(ntModule.Name, symbolName));
        }
    }
}